#include <stdint.h>
#include "f1c100s_clock.h"
#include "sys_dram.h"
#include "io.h"

#define PLL_DDR_CLK (128000000)
#define SDR_T_CAS (0x2)
#define SDR_T_RAS (0x8)
#define SDR_T_RCD (0x3)
#define SDR_T_RP (0x3)
#define SDR_T_WR (0x3)
#define SDR_T_RFC (0xd)
#define SDR_T_XSR (0xf9)
#define SDR_T_RC (0xb)
#define SDR_T_INIT (0x8)
#define SDR_T_INIT_REF (0x7)
#define SDR_T_WTR (0x2)
#define SDR_T_RRD (0x2)
#define SDR_T_XP (0x0)

enum dram_type_t {
    DRAM_TYPE_SDR  = 0,
    DRAM_TYPE_DDR  = 1,
    DRAM_TYPE_MDDR = 2,
};

struct dram_para_t {
    uint32_t base; /* dram base address */
    uint32_t size; /* dram size (unit: MByte) */
    uint32_t clk; /* dram work clock (unit: MHz) */
    uint32_t access_mode; /* 0: interleave mode 1: sequence mode */
    uint32_t cs_num; /* dram chip count  1: one chip  2: two chip */
    uint32_t ddr8_remap; /* for 8bits data width DDR 0: normal  1: 8bits */
    enum dram_type_t sdr_ddr;
    uint32_t bwidth; /* dram bus width */
    uint32_t col_width; /* column address width */
    uint32_t row_width; /* row address width */
    uint32_t bank_size; /* dram bank count */
    uint32_t cas; /* dram cas */
};

static inline void sdelay(int loops) {
    __asm__ __volatile__("1:\n"
                         "subs %0, %1, #1\n"
                         "bne 1b"
                         : "=r"(loops)
                         : "0"(loops));
}

static void dram_delay(int ms) {
    sdelay(ms * 2 * 1000);
}

static int dram_initial(void) {
    unsigned int time = 0xffffff;

    write32(DRAMC_BASE + DRAM_SCTLR, read32(DRAMC_BASE + DRAM_SCTLR) | 0x1);
    while((read32(DRAMC_BASE + DRAM_SCTLR) & 0x1) && time--) {
        if(time == 0) return 0;
    }
    return 1;
}

static int dram_delay_scan(void) {
    unsigned int time = 0xffffff;

    write32(DRAMC_BASE + DRAM_DDLYR, read32(DRAMC_BASE + DRAM_DDLYR) | 0x1);
    while((read32(DRAMC_BASE + DRAM_DDLYR) & 0x1) && time--) {
        if(time == 0) return 0;
    }
    return 1;
}

static void dram_set_autofresh_cycle(uint32_t clk) {
    uint32_t val  = 0;
    uint32_t row  = 0;
    uint32_t temp = 0;

    row = read32(DRAMC_BASE + DRAM_SCONR);
    row &= 0x1e0;
    row >>= 0x5;

    if(row == 0xc) {
        if(clk >= 1000000) {
            temp = clk + (clk >> 3) + (clk >> 4) + (clk >> 5);
            while(temp >= (10000000 >> 6)) {
                temp -= (10000000 >> 6);
                val++;
            }
        } else {
            val = (clk * 499) >> 6;
        }
    } else if(row == 0xb) {
        if(clk >= 1000000) {
            temp = clk + (clk >> 3) + (clk >> 4) + (clk >> 5);
            while(temp >= (10000000 >> 7)) {
                temp -= (10000000 >> 7);
                val++;
            }
        } else {
            val = (clk * 499) >> 5;
        }
    }
    write32(DRAMC_BASE + DRAM_SREFR, val);
}

static int dram_para_setup(struct dram_para_t* para) {
    uint32_t val = 0;

    val = (para->ddr8_remap) | (0x1 << 1) | ((para->bank_size >> 2) << 3) |
          ((para->cs_num >> 1) << 4) | ((para->row_width - 1) << 5) |
          ((para->col_width - 1) << 9) |
          ((para->sdr_ddr ? (para->bwidth >> 4) : (para->bwidth >> 5)) << 13) |
          (para->access_mode << 15) | (para->sdr_ddr << 16);

    write32(DRAMC_BASE + DRAM_SCONR, val);
    write32(DRAMC_BASE + DRAM_SCTLR, read32(DRAMC_BASE + DRAM_SCTLR) | (0x1 << 19));
    return dram_initial();
}

static uint32_t dram_check_delay(uint32_t bwidth) {
    uint32_t dsize;
    uint32_t i, j;
    uint32_t num   = 0;
    uint32_t dflag = 0;

    dsize = ((bwidth == 16) ? 4 : 2);
    for(i = 0; i < dsize; i++) {
        if(i == 0)
            dflag = read32(DRAMC_BASE + DRAM_DRPTR0);
        else if(i == 1)
            dflag = read32(DRAMC_BASE + DRAM_DRPTR1);
        else if(i == 2)
            dflag = read32(DRAMC_BASE + DRAM_DRPTR2);
        else if(i == 3)
            dflag = read32(DRAMC_BASE + DRAM_DRPTR3);

        for(j = 0; j < 32; j++) {
            if(dflag & 0x1) num++;
            dflag >>= 1;
        }
    }
    return num;
}

static int sdr_readpipe_scan(void) {
    uint32_t k = 0;

    for(k = 0; k < 32; k++) {
        write32(0x80000000 + 4 * k, k);
    }
    for(k = 0; k < 32; k++) {
        if(read32(0x80000000 + 4 * k) != k) return 0;
    }
    return 1;
}

static uint32_t sdr_readpipe_select(void) {
    uint32_t value = 0;
    uint32_t i     = 0;
    for(i = 0; i < 8; i++) {
        write32(
            DRAMC_BASE + DRAM_SCTLR, (read32(DRAMC_BASE + DRAM_SCTLR) & (~(0x7 << 6))) | (i << 6));
        if(sdr_readpipe_scan()) {
            value = i;
            return value;
        }
    }
    return value;
}

static uint32_t dram_check_type(struct dram_para_t* para) {
    uint32_t val   = 0;
    uint32_t times = 0;
    uint32_t i;

    for(i = 0; i < 8; i++) {
        val = read32(DRAMC_BASE + DRAM_SCTLR);
        val &= ~(0x7 << 6);
        val |= (i << 6);
        write32(DRAMC_BASE + DRAM_SCTLR, val);

        dram_delay_scan();
        if(read32(DRAMC_BASE + DRAM_DDLYR) & 0x30) times++;
    }

    if(times == 8) {
        para->sdr_ddr = DRAM_TYPE_SDR;
        return 0;
    } else {
        para->sdr_ddr = DRAM_TYPE_DDR;
        return 1;
    }
}

static uint32_t dram_scan_readpipe(struct dram_para_t* para) {
    uint32_t i, rp_best = 0, rp_val = 0;
    uint32_t val = 0;
    uint32_t readpipe[8];

    if(para->sdr_ddr == DRAM_TYPE_DDR) {
        for(i = 0; i < 8; i++) {
            val = read32(DRAMC_BASE + DRAM_SCTLR);
            val &= ~(0x7 << 6);
            val |= (i << 6);
            write32(DRAMC_BASE + DRAM_SCTLR, val);
            dram_delay_scan();
            readpipe[i] = 0;
            if((((read32(DRAMC_BASE + DRAM_DDLYR) >> 4) & 0x3) == 0x0) &&
               (((read32(DRAMC_BASE + DRAM_DDLYR) >> 4) & 0x1) == 0x0)) {
                readpipe[i] = dram_check_delay(para->bwidth);
            }
            if(rp_val < readpipe[i]) {
                rp_val  = readpipe[i];
                rp_best = i;
            }
        }
        val = read32(DRAMC_BASE + DRAM_SCTLR);
        val &= ~(0x7 << 6);
        val |= (rp_best << 6);
        write32(DRAMC_BASE + DRAM_SCTLR, val);
        dram_delay_scan();
    } else {
        val = read32(DRAMC_BASE + DRAM_SCONR);
        val &= (~(0x1 << 16));
        val &= (~(0x3 << 13));
        write32(DRAMC_BASE + DRAM_SCONR, val);
        rp_best = sdr_readpipe_select();
        val     = read32(DRAMC_BASE + DRAM_SCTLR);
        val &= ~(0x7 << 6);
        val |= (rp_best << 6);
        write32(DRAMC_BASE + DRAM_SCTLR, val);
    }
    return 0;
}

static uint32_t dram_get_dram_size(struct dram_para_t* para) {
    uint32_t colflag = 10, rowflag = 13;
    uint32_t i     = 0;
    uint32_t val1  = 0;
    uint32_t count = 0;
    uint32_t addr1, addr2;

    para->col_width = colflag;
    para->row_width = rowflag;
    dram_para_setup(para);
    dram_scan_readpipe(para);
    for(i = 0; i < 32; i++) {
        *((uint32_t*)(0x80000200 + i)) = 0x11111111;
        *((uint32_t*)(0x80000600 + i)) = 0x22222222;
    }
    for(i = 0; i < 32; i++) {
        val1 = *((uint32_t*)(0x80000200 + i));
        if(val1 == 0x22222222) count++;
    }
    if(count == 32) {
        colflag = 9;
    } else {
        colflag = 10;
    }
    count           = 0;
    para->col_width = colflag;
    para->row_width = rowflag;
    dram_para_setup(para);
    if(colflag == 10) {
        addr1 = 0x80400000;
        addr2 = 0x80c00000;
    } else {
        addr1 = 0x80200000;
        addr2 = 0x80600000;
    }
    for(i = 0; i < 32; i++) {
        *((uint32_t*)(addr1 + i)) = 0x33333333;
        *((uint32_t*)(addr2 + i)) = 0x44444444;
    }
    for(i = 0; i < 32; i++) {
        val1 = *((uint32_t*)(addr1 + i));
        if(val1 == 0x44444444) {
            count++;
        }
    }
    if(count == 32) {
        rowflag = 12;
    } else {
        rowflag = 13;
    }
    para->col_width = colflag;
    para->row_width = rowflag;
    if(para->row_width != 13) {
        para->size = 16;
    } else if(para->col_width == 10) {
        para->size = 64;
    } else {
        para->size = 32;
    }
    dram_set_autofresh_cycle(para->clk);
    para->access_mode = 0;
    dram_para_setup(para);

    return 0;
}

static int dram_init(struct dram_para_t* para) {
    uint32_t val = 0;
    uint32_t i;

    write32(GPIO_BASE + 0x24, read32(GPIO_BASE + 0x24) | (0x7 << 12));
    dram_delay(5);
    if(((para->cas) >> 3) & 0x1) {
        write32(GPIO_BASE + 0x2c4, read32(GPIO_BASE + 0x2c4) | (0x1 << 23) | (0x20 << 17));
    }
    if((para->clk >= 144) && (para->clk <= 180)) {
        write32(GPIO_BASE + 0x2c0, 0xaaa);
    }
    if(para->clk >= 180) {
        write32(GPIO_BASE + 0x2c0, 0xfff);
    }
    if((para->clk) <= 96) {
        val = (0x1 << 0) | (0x0 << 4) | (((para->clk * 2) / 12 - 1) << 8) | (0x1u << 31);
    } else {
        val = (0x0 << 0) | (0x0 << 4) | (((para->clk * 2) / 24 - 1) << 8) | (0x1u << 31);
    }

    if(para->cas & (0x1 << 4)) {
        write32(CCU_BASE + CCU_PLL_DDR0_PAT, 0xd1303333);
    } else if(para->cas & (0x1 << 5)) {
        write32(CCU_BASE + CCU_PLL_DDR0_PAT, 0xcce06666);
    } else if(para->cas & (0x1 << 6)) {
        write32(CCU_BASE + CCU_PLL_DDR0_PAT, 0xc8909999);
    } else if(para->cas & (0x1 << 7)) {
        write32(CCU_BASE + CCU_PLL_DDR0_PAT, 0xc440cccc);
    }
    if(para->cas & (0xf << 4)) {
        val |= 0x1 << 24;
    }
    write32(CCU_BASE + CCU_PLL_DDR_CTRL, val);
    write32(CCU_BASE + CCU_PLL_DDR_CTRL, read32(CCU_BASE + CCU_PLL_DDR_CTRL) | (0x1 << 20));
    while((read32(CCU_BASE + CCU_PLL_DDR_CTRL) & (1 << 28)) == 0)
        ;
    dram_delay(5);
    write32(CCU_BASE + CCU_BUS_CLK_GATE0, read32(CCU_BASE + CCU_BUS_CLK_GATE0) | (0x1 << 14));
    write32(CCU_BASE + CCU_BUS_SOFT_RST0, read32(CCU_BASE + CCU_BUS_SOFT_RST0) & ~(0x1 << 14));
    for(i = 0; i < 10; i++) continue;
    write32(CCU_BASE + CCU_BUS_SOFT_RST0, read32(CCU_BASE + CCU_BUS_SOFT_RST0) | (0x1 << 14));

    val = read32(GPIO_BASE + 0x2c4);
    (para->sdr_ddr == DRAM_TYPE_DDR) ? (val |= (0x1 << 16)) : (val &= ~(0x1 << 16));
    write32(GPIO_BASE + 0x2c4, val);

    val = (SDR_T_CAS << 0) | (SDR_T_RAS << 3) | (SDR_T_RCD << 7) | (SDR_T_RP << 10) |
          (SDR_T_WR << 13) | (SDR_T_RFC << 15) | (SDR_T_XSR << 19) | (SDR_T_RC << 28);
    write32(DRAMC_BASE + DRAM_STMG0R, val);
    val = (SDR_T_INIT << 0) | (SDR_T_INIT_REF << 16) | (SDR_T_WTR << 20) | (SDR_T_RRD << 22) |
          (SDR_T_XP << 25);
    write32(DRAMC_BASE + DRAM_STMG1R, val);
    dram_para_setup(para);
    dram_check_type(para);

    val = read32(GPIO_BASE + 0x2c4);
    (para->sdr_ddr == DRAM_TYPE_DDR) ? (val |= (0x1 << 16)) : (val &= ~(0x1 << 16));
    write32(GPIO_BASE + 0x2c4, val);

    dram_set_autofresh_cycle(para->clk);
    dram_scan_readpipe(para);
    dram_get_dram_size(para);

    for(i = 0; i < 128; i++) {
        *((volatile uint32_t*)(para->base + 4 * i)) = para->base + 4 * i;
    }

    for(i = 0; i < 128; i++) {
        if(*((volatile uint32_t*)(para->base + 4 * i)) != (para->base + 4 * i)) return 0;
    }
    return 1;
}

uint8_t sys_dram_init(void) {
    struct dram_para_t dram;

    dram.base        = 0x80000000;
    dram.size        = 0;
    dram.clk         = PLL_DDR_CLK / 1000000;
    dram.access_mode = 1;
    dram.cs_num      = 1;
    dram.ddr8_remap  = 0;
    dram.sdr_ddr     = DRAM_TYPE_DDR;
    dram.bwidth      = 16;
    dram.col_width   = 10;
    dram.row_width   = 13;
    dram.bank_size   = 4;
    dram.cas         = 0x3;

    if(dram_init(&dram))
        return dram.size;
    else
        return 0;
}
